/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package elsimulatorjava;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import java.sql.*;
import java.util.Scanner;

/**
 *
 * @author corey
 */
public class Building implements Runnable{
    private ArrayList<Person> people;
    private ArrayList<Floor> floors; //Holds a counter of the people on the floor
    private ArrayList<Elevator> elevators;
    
    private int numberOfPeople;
    
    private Set<Integer> upQueue;
    private Set<Integer> downQueue;
    
    private Date timer;
    
    private boolean stopSimulation = false;
    
    public Building(){
        this.people = new ArrayList<Person>();
        this.floors = new ArrayList<Floor>();
        this.elevators = new ArrayList<Elevator>();
        this.upQueue = new HashSet<Integer>();
        this.downQueue = new HashSet<Integer>();
        this.timer = new Date();
    }
    
    public Building (ArrayList<Elevator> e, ArrayList<Floor> f, int n){
        this.floors = f;
        this.elevators = e;
        this.people = new ArrayList<Person>();
        this.numberOfPeople = n;
        
        this.upQueue = new HashSet<Integer>();
        this.downQueue = new HashSet<Integer>();
        this.timer = new Date();

    }
    
    public Building(ArrayList<Elevator> e, ArrayList<Floor> f){
        this.elevators = e;
        this.floors = f;
        this.people = new ArrayList<Person>();
        this.upQueue = new HashSet<Integer>();
        this.downQueue = new HashSet<Integer>();
        this.timer = new Date();
    }
    
    /*
     * Called by a person, to request the elevator to come to the floor
     */
    Boolean requestElevator(boolean goUp, int floorID){
        boolean elevatorRequested = false;
        int deltaFloors = Integer.MAX_VALUE;
        int nearestElevator = -1;
        for (int i = 0; i < this.elevators.size(); i++){
            if (goUp && this.elevators.get(i).getCurrentFloor() != floorID){
                if (this.elevators.get(i).goesToFloor(floorID) && this.elevators.get(i).isGoingUp() && this.elevators.get(i).getCurrentFloor() <= floorID){
                    if (java.lang.Math.abs(this.elevators.get(i).getCurrentFloor()-floorID) < deltaFloors){
                        deltaFloors = java.lang.Math.abs(this.elevators.get(i).getCurrentFloor()-floorID);
                        nearestElevator = i;
                    }
                } else if (this.elevators.get(i).goesToFloor(floorID) && (this.elevators.get(i).isQueueEmpty() || 
                        this.elevators.get(i).getCurrentFloor() >= this.elevators.get(i).getMaxFloor())){
                        this.upQueue.add(floorID);
                        elevatorRequested = true;
                    } else if (!this.elevators.get(i).isQueueEmpty() && (!this.elevators.get(i).isGoingUp() || (this.elevators.get(i).isGoingUp() && this.elevators.get(i).getCurrentFloor() > floorID))){
                        this.upQueue.add(floorID);
                        elevatorRequested = true;
                }
            } else {          
                if (this.elevators.get(i).goesToFloor(floorID) && !this.elevators.get(i).isGoingUp() && this.elevators.get(i).getCurrentFloor() >= floorID){
                        if (java.lang.Math.abs(this.elevators.get(i).getCurrentFloor()-floorID) < deltaFloors){
                            deltaFloors = java.lang.Math.abs(this.elevators.get(i).getCurrentFloor()-floorID);
                            nearestElevator = i;
                    } 
                } else if (this.elevators.get(i).goesToFloor(floorID) && (this.elevators.get(i).isQueueEmpty() || 
                        this.elevators.get(i).getCurrentFloor() <= this.elevators.get(i).getMinFloor())){
                        downQueue.add(floorID);
                        elevatorRequested = true;
                    } else if (!this.elevators.get(i).isQueueEmpty() && (this.elevators.get(i).isGoingUp() || (!this.elevators.get(i).isGoingUp() && this.elevators.get(i).getCurrentFloor() > floorID))){
                        downQueue.add(floorID);
                        elevatorRequested = true;
                }
            }
        }
        if (nearestElevator != -1){
            this.elevators.get(nearestElevator).addToQueue(floorID);
            elevatorRequested = true;
        }
        
        return elevatorRequested;
    }
    
    /*
     * Moves people from the elevator onto the floor
     */
    /*void moveToFloor(){
        for (int i = 0; i < people.size(); i++){
            if (people.get(i).inElevator()){
                if (!people.get(i).getElevator().isMoving() && !people.get(i).getElevator().isDoorClosed()){
                    if (people.get(i).getFutureFloor() == people.get(i).getElevator().getCurrentFloor()){
                        people.get(i).getElevator().removePerson(); // Remove a person from the elevator counter
                        people.get(i).changeFloor(people.get(i).getElevator().getCurrentFloor()); // Give them the floor
                        this.addPersonToFloor(people.get(i).getElevator().getCurrentFloor()); // Add person to floor counter
                        people.get(i).setInElevator(false); // Remove the elevator
                    }
                }
            }
        }
    }*/
    
    /*
     * Moves people from the floor to the elevator
     */
    void movePeople(){
        for (int i = 0; i < this.people.size(); i++){
            if (this.people.get(i).inElevator()){
                if (!this.people.get(i).getElevatorIsMoving() && !this.people.get(i).getElevatorIsDoorClosed()){
                    if (this.people.get(i).getFutureFloor() == this.people.get(i).getElevatorCurrentFloor()){
                        this.people.get(i).removePersonFromElevator(); // Remove a person from the elevator counter
                        this.people.get(i).changeFloor(this.people.get(i).getElevatorCurrentFloor()); // Give them the floor
                        this.addPersonToFloor(this.people.get(i).getElevatorCurrentFloor()); // Add person to floor counter
                        this.people.get(i).setInElevator(false); // Remove the elevator
                    }
                }
            }
        }
        
        for (int i = 0; i < this.elevators.size(); i++){
            if (!this.elevators.get(i).isMoving() && !this.elevators.get(i).isDoorClosed()){
                for (int j = 0; j < people.size(); j++){
                    if (this.elevators.get(i).getCurrentFloor() == people.get(j).getCurrentFloor() && !this.people.get(j).inElevator()){
                        if (this.people.get(j).getCurrentFloor() < this.people.get(j).getFutureFloor() && this.elevators.get(i).isGoingUp()
                                && this.elevators.get(i).goesToFloor(this.people.get(j).getFutureFloor())){
                                this.removePersonFromFloor(people.get(j).getCurrentFloor()); // Remove person from the floor counter
                                this.people.get(j).setElevator(elevators.get(i));
                                this.people.get(j).setInElevator(true);
                                this.elevators.get(i).addPerson(); // Add a person to the elevator counter
                                this.elevatorCall(i, this.people.get(j).getFutureFloor());
                        } else if (this.people.get(j).getCurrentFloor() > this.people.get(j).getFutureFloor() && !this.elevators.get(i).isGoingUp()
                                && this.elevators.get(i).goesToFloor(people.get(j).getFutureFloor())){
                                this.removePersonFromFloor(this.people.get(j).getCurrentFloor()); // Remove person from the floor counter
                                this.people.get(j).setElevator(this.elevators.get(i));
                                this.people.get(j).setInElevator(true);
                                this.elevators.get(i).addPerson(); // Add a person to the elevator counter
                                this.elevatorCall(i, this.people.get(j).getFutureFloor());
                        }
                        
                    }
                }
                
            }
            do{
                this.elevators.get(i).setDoorClosed(true);
            } while (!elevators.get(i).isDoorClosed());
        }
    }
    
    void removePersonFromFloor(int floorID){
        this.floors.get(floorID).decrementPeople();
        //this.floors.set(floorID, (floors.get(floorID)-1));
    }
    
    void addPersonToFloor(int floorID){
        this.floors.get(floorID).incrementPeople();
        //this.floors.set(floorID, (floors.get(floorID)+1));
    }
    
    int getPeopleOnFloor(int floorID){
        return this.floors.get(floorID).getNumberOfPeople();
    }
    
    void createElevator(int minFloor, int maxFloor, ArrayList<Boolean> floorList){
        this.elevators.add(new Elevator(minFloor, maxFloor, floorList));
        for (int i = 0; i < (maxFloor-minFloor+1); i++){
            this.floors.add(new Floor("Floor " +i));
        }
        //this.createFloors(maxFloor-minFloor+1);
    }
    
    void removeElevator(Elevator e){
        this.elevators.remove(e);
    }
    
    void createFloors(ArrayList<String> floorNames){
        for (int i = 0; i < floorNames.size(); i++){
            this.floors.add(new Floor(floorNames.get(i)));
        }
    }
    
    void createPerson(int startFloor, int endFloor){
        this.people.add(new Person(this, startFloor, endFloor));
    }
    
    void elevatorCall(int elIndex, int floorID){
        this.elevators.get(elIndex).addToQueue(floorID);
    }
    
   /* void start(){
        
        this.createPerson(3, 4);
        this.createPerson(1, 2);
        this.createPerson(0, 5);
        this.createPerson(5, 2);
        this.createPerson(4, 0);
        while (true){
            if (new Date().getTime() - timer.getTime() > 20000 && flag){
                this.createPerson(3,1);
                flag = false;
            }
            for (int i = 0; i < people.size(); i++){
                people.get(i).callElevator();
            }
            for (int i = 0; i < elevators.size(); i++){
                if (!elevators.get(i).isDoorClosed() && !elevators.get(i).isMoving()){
                      //  this.moveToFloor();
                        this.movePeople();
                   // System.out.println("aaa");
                } else if (elevators.get(i).isDoorClosed() && !elevators.get(i).isMoving()){
                    elevators.get(i).elevatorAction();
                    //System.out.println("bbb");
                } else if (elevators.get(i).isDoorClosed() && elevators.get(i).isMoving()){
                 //  while (elevators.get(i).isDoorClosed() && elevators.get(i).isMoving()){
                        elevators.get(i).moveElevator();
                        //System.out.println("ccc");
                  // }
                }
            }
        }
    }*/
    
    Date getTime(){
        return this.timer;
    }
    
    boolean getElevatorGoToFloor(int elIndex, int floorID){
        return this.elevators.get(elIndex).goesToFloor(floorID);
    }
    
    int getElevatorCurrentFloor(int elIndex){
        return this.elevators.get(elIndex).getCurrentFloor();
    }
    
    boolean isElevatorGoingUp(int elIndex){
        return this.elevators.get(elIndex).isGoingUp();
    }
    
    int getElevatorCount(){
        return this.elevators.size();
    }
    
    void setStopElevator(int elIndex, boolean stop){
        this.elevators.get(elIndex).setEmergencyStop(stop);
    }
   /* 
    void setCurrentFloorEmitter(int elIndex, Application a, String method){
        this.elevators.get(elIndex).currentFloorChanged.connect(a, method);
    }
    
    void setDoorStateEmitter(int elIndex, Application a, String method){
        this.elevators.get(elIndex).doorStateChanged.connect(a, method);
    }
    
    void setDirectionEmitter(int elIndex, Application a, String method){
        this.elevators.get(elIndex).directionChanged.connect(a, method);
    }
    
    void setPeopleCountEmitter(int elIndex, Application a, String method){
        this.elevators.get(elIndex).peopleInElevatorChanged.connect(a, method);
    }*/
    

    public void run() {
        for (int i = 0; i < this.numberOfPeople; i++){
            this.people.add(new Person(this));
        }
        do {
            for (int i = 0; i < this.people.size(); i++){
                if (!this.people.get(i).hasCalledElevator()){
                    this.people.get(i).callElevator();
                }
            }
            for (int i = 0; i < this.elevators.size(); i++){
                if (!this.elevators.get(i).isDoorClosed() && !this.elevators.get(i).isMoving()){
                        this.movePeople();
                } else if (this.elevators.get(i).isDoorClosed() && !this.elevators.get(i).isMoving()){
                    this.elevators.get(i).elevatorAction();
                } else if (this.elevators.get(i).isDoorClosed() && this.elevators.get(i).isMoving()){
                        this.elevators.get(i).moveElevator();
                }
                this.refreshQueue();
            }
        } while(!stopSimulation);
    }

    int getNumberOfFloors() {
        return this.floors.size();
    }
    
    void setFloors(ArrayList<Floor> f) {
        this.floors = f;
    }
    
    ArrayList<Floor> getFloors(){
        return this.floors;
    }
    
    void setElevators(ArrayList<Elevator> e){
        this.elevators = e;
    }
    
    void refreshQueue(){
        for (int i = 0; i < this.elevators.size(); i++){
            if (this.elevators.get(i).isQueueEmpty() && !this.upQueue.isEmpty() && this.elevators.get(i).isGoingUp()){
                this.passQueue(i, this.upQueue);
            } else if (this.elevators.get(i).isQueueEmpty() && !this.downQueue.isEmpty() && !this.elevators.get(i).isGoingUp()){
                this.passQueue(i, this.downQueue);
            } else if (!this.upQueue.isEmpty() && !this.elevators.get(i).isGoingUp() && (this.elevators.get(i).isQueueEmpty() || 
                        this.elevators.get(i).getCurrentFloor() >= this.elevators.get(i).getMaxFloor())){
                this.elevators.get(i).changeDirection();
                this.passQueue(i, this.upQueue);
            } else if (!this.downQueue.isEmpty() && this.elevators.get(i).isGoingUp() && (this.elevators.get(i).isQueueEmpty() || 
                        this.elevators.get(i).getCurrentFloor() <= this.elevators.get(i).getMinFloor())){
                this.elevators.get(i).changeDirection();
                this.passQueue(i, this.downQueue);
            }
        }
    }
    
    void passQueue(int elIndex, Set q){
            Iterator it = q.iterator();
            while (it.hasNext()){
                this.elevators.get(elIndex).addToQueue((Integer)it.next());
                it.remove();
            }
            
    }
    
    public ArrayList<Elevator> getElevators(){
        return this.elevators;
    }
    
    void stop(){
        this.stopSimulation = true;
    }

    void start() {
        this.stopSimulation = false;
    }

    void save() throws Exception {
        Class.forName("org.sqlite.JDBC");
        Connection conn = DriverManager.getConnection("jdbc:sqlite:saved1.db");
        
        Statement peopleDB = conn.createStatement();
        peopleDB.executeUpdate("drop table if exists People;");
        peopleDB.executeUpdate("create table People (Name, CurrentFloor, FutureFloor, Elevator, InElevator, CalledElevator, Timer);");
        PreparedStatement prepPeople = conn.prepareStatement("insert into People values (?, ?, ?, ?, ?, ?, ?)");

        for(Person person : people) {
            prepPeople.setString(1, person.getName());
            prepPeople.setInt(2, person.getCurrentFloor());
            prepPeople.setInt(3, person.getFutureFloor());
            prepPeople.setInt(4, person.getElevatorIndex());
            prepPeople.setBoolean(5, person.inElevator());
            prepPeople.setBoolean(6, person.hasCalledElevator());
            prepPeople.setLong(7, person.getTimer());
            prepPeople.addBatch();
        }

        conn.setAutoCommit(false);
        prepPeople.executeBatch();
        System.out.println("test");
        prepPeople.clearBatch();
        Statement elevatorDB = conn.createStatement();
        elevatorDB.executeUpdate("drop table if exists Elevator;");
        elevatorDB.executeUpdate("create table Elevator(Elevator_Index, "
                + "Floors_not_allowed, Elevator_queue, Timer, Door_Timer, "
                + "Number_of_people_inside, Capacity, Minimum_floor, Maximum_floor, "
                + "Current_floor, Moving, Going_up, Door_closed, Door_closed_attempt, "
                + "Emergency_stop, PRIMARY KEY (field1))");
        PreparedStatement prepElevator = conn.prepareStatement("insert into Elevator (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
        for (Elevator elevator : elevators) {
            prepElevator.setInt(1, elevator.getIndex());
            String allowedFloorsElevators = "";
            for (Boolean allowedFloor : elevator.getFloorList()) {
                allowedFloorsElevators += allowedFloor + " ";
            }
            prepElevator.setString(2, allowedFloorsElevators);

            String elevatorQueue = "";
            for (Integer wantsToGoTo : elevator.getQueue()) {
                elevatorQueue += wantsToGoTo + " ";
            }
            prepElevator.setString(3, elevatorQueue);

            prepElevator.setLong(4, elevator.getTimer());
            prepElevator.setLong(5, elevator.getDoorTimer());
            prepElevator.setInt(6, elevator.getPeople());
            prepElevator.setInt(7, elevator.getCapacity());
            prepElevator.setInt(8, elevator.getMinFloor());
            prepElevator.setInt(9, elevator.getMaxFloor());
            prepElevator.setInt(10, elevator.getCurrentFloor());
            prepElevator.setBoolean(11, elevator.isMoving());
            prepElevator.setBoolean(12, elevator.isGoingUp());
            prepElevator.setBoolean(13, elevator.isDoorClosed());
            prepElevator.setBoolean(14, elevator.getDoorActionAttempt());
            prepElevator.setBoolean(15, elevator.getEmergencyStop());
            prepElevator.addBatch();
        }
        prepElevator.executeBatch();

        prepElevator.clearBatch();
        Statement floorDB = conn.createStatement();
        floorDB.executeUpdate("drop table if exists Floor");
        floorDB.executeUpdate("create table Floor(Floor_name, Floor_index,"
                + "Number_of_people_on_floor)");
        PreparedStatement prepFloor = conn.prepareStatement("insert into Floor(?, ?, ?)");

        for(Floor floor : floors) {
            prepFloor.setString(1, floor.getFloorName());
            prepFloor.setInt(2, floor.getIndex());
            prepFloor.setInt(3, floor.getNumberOfPeople());
            prepFloor.addBatch();
        }

        prepFloor.executeBatch();
        prepFloor.clearBatch();
        conn.close();
    }

    public void load() throws Exception {
        Class.forName("org.sqlite.JDBC");
        Connection conn = DriverManager.getConnection("jdbc:sqlite:saved.db");
        Statement building = conn.createStatement();
        ResultSet accessData = building.executeQuery("select * from Floor");
        floors.clear();
        Floor.resetIndex();
        while (accessData.next()) {
            floors.add(new Floor(accessData.getString(1)));
            floors.get(accessData.getInt(2)).setNumberOfPeople(
                    accessData.getInt(3));
        }

        accessData = building.executeQuery("select * from Elevator");
        elevators.clear();
        Elevator.resetIndex();

        while (accessData.next()) {
            elevators.add(new Elevator(accessData.getInt(8),
                    accessData.getInt(9),
                    stringToBooleanList(accessData.getString(2))));
            elevators.get(elevators.size()-1).setTimer(accessData.getLong(4));
            elevators.get(elevators.size()-1).setDoorTimer(accessData.getLong(5));
            elevators.get(elevators.size()-1).setNumberOfPeople(accessData.getInt(6));
            elevators.get(elevators.size()-1).setCapacity(accessData.getInt(7));
            elevators.get(elevators.size()-1).setCurrentFloor(accessData.getInt(10));
            elevators.get(elevators.size()-1).setMoving(accessData.getBoolean(11));
            elevators.get(elevators.size()-1).setDoorClosed(accessData.getBoolean(13));
            elevators.get(elevators.size()-1).setDoorClosedAttempt(accessData.getBoolean(14));
            elevators.get(elevators.size()-1).setEmergencyStop(accessData.getBoolean(15));
            elevators.get(elevators.size()-1).setQueue(stringToIntegerList(accessData.getString(3)));
        }

        accessData = building.executeQuery("select * from Building");
        while (accessData.next()) {
            this.setNumberOfPeople(accessData.getInt(1));
            this.setUpQueue(stringToIntegerList(accessData.getString(2)));
            this.setDownQueue(stringToIntegerList(accessData.getString(3)));
            this.setTimer(accessData.getLong(4));
            this.setStopSimulation(accessData.getBoolean(5));
        }

        accessData = building.executeQuery("select * from People");
        people.clear();

        while (accessData.next()) {
            people.add(new Person(this, accessData.getInt(2), accessData.getInt(3)));
            people.get(people.size()-1).setElevator(elevators.get(accessData.getInt(4)));
            people.get(people.size()-1).setInElevator(accessData.getBoolean(5));
            people.get(people.size()-1).setHasCalledElevator(accessData.getBoolean(6));
        }

    }

    public void setNumberOfPeople(int number) {
        numberOfPeople = number;
    }

    public void setUpQueue(Set<Integer> queueUp) {
        upQueue = queueUp;
    }

    public void setDownQueue(Set<Integer> queueDown) {
        downQueue = queueDown;
    }

    public void setTimer(Long time) {
        timer.setTime(time);
    }

    public void setStopSimulation(Boolean stop) {
        stopSimulation = stop;
    }

    private ArrayList<Boolean> stringToBooleanList(String booleanValues){
        Scanner reader = new Scanner(booleanValues);
        ArrayList<Boolean> booleanList = null;
        String test;
        while (reader.hasNext()) {
            test = reader.next();
            if (test.equals("true")) {
                booleanList.add(true);
            }
            else {
                booleanList.add(false);
            }
        }
        return booleanList;
    }

    private Set<Integer> stringToIntegerList(String integerValues) {
        Scanner reader = new Scanner(integerValues);
        Set<Integer> integerList = null;
        while (reader.hasNext()) {
            integerList.add(reader.nextInt());
        }
        return integerList;
    }

}
